Skip to content

feat(authz): add roles table for Audit user page#106

Merged
dcoa merged 9 commits intoopenedx:masterfrom
WGU-Open-edX:audit-user
Apr 18, 2026
Merged

feat(authz): add roles table for Audit user page#106
dcoa merged 9 commits intoopenedx:masterfrom
WGU-Open-edX:audit-user

Conversation

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor

@jacobo-dominguez-wgu jacobo-dominguez-wgu commented Mar 23, 2026

Description

Creating the roles table on the audit user page. It includes:

  • New roles assignments table replacing the card roles.
  • Different background color for rows including roles from django.
  • Table pagination.
  • Minimal modifications to the header to match the ui design.

It closes #86
Figma design

Pending things:

Warning

Will be on draft status until the required endpoints are completed.
This PR requires some the endpoints proposed in the section "Proposed Endpoint for M2"
openedx/openedx-authz#230

image image

How to test it

Pre - requisites

A running local tutor instance.
It requires the endpoint created on this PR openedx/openedx-authz#230 (already merged)
Since the role assignation wizard is currently on development, we can still use the old library specific access manager and assign some roles there http://apps.local.openedx.io:2025/admin-console/authz/libraries/:libraryId
or, can assign roles by directly using the api endpoint http://local.openedx.io:8000/api-docs/#/authz/authz_v1_roles_users_update

1 - Go to the new audit user page -> http://apps.local.openedx.io:2025/admin-console/authz/user/:username (select a user with many roles assigned to get a more populated table) (NOTE: The team members table will redirect to this page, currently in progress #124)
2 - Validate all the data loads properly, the UI matches the design and acceptance criteria is fulfilled.

NOTE: Expand all permissions and delete functionality are on different prs.
NOTE 2: The "Assign Role" button redirects to the new role assignation wizard (in development here #109 and #111).

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Mar 23, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented Mar 23, 2026

Thanks for the pull request, @jacobo-dominguez-wgu!

This repository is currently maintained by @openedx/committers-frontend.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Mar 23, 2026
@jacobo-dominguez-wgu jacobo-dominguez-wgu marked this pull request as draft March 23, 2026 16:53
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Mar 23, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Mar 23, 2026
@jacobo-dominguez-wgu jacobo-dominguez-wgu force-pushed the audit-user branch 3 times, most recently from 2151f75 to ef7bf9b Compare March 31, 2026 01:30
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 97.60000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.70%. Comparing base (067b971) to head (4bc061a).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/authz-module/data/api.ts 85.71% 2 Missing ⚠️
src/index.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #106      +/-   ##
==========================================
+ Coverage   95.63%   95.70%   +0.06%     
==========================================
  Files          68       69       +1     
  Lines        1466     1536      +70     
  Branches      319      321       +2     
==========================================
+ Hits         1402     1470      +68     
- Misses         62       64       +2     
  Partials        2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jacobo-dominguez-wgu jacobo-dominguez-wgu force-pushed the audit-user branch 2 times, most recently from 808d584 to 045db70 Compare April 14, 2026 15:54
@jacobo-dominguez-wgu jacobo-dominguez-wgu marked this pull request as ready for review April 14, 2026 16:26
Copy link
Copy Markdown
Contributor

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started by trying to test this. The page renders correctly:

Image

But when I try to assign roles by clicking on "Assign Role", I get a bunch of errors seemingly stemming from 404s. Have the endpoints not been implemented, yet? I'm using the very latest edx-platform as of the time of this review.

If that's the case, please add the backend PRs that this depends on to the description. Otherwise, if there's some other way to test this, please outline the steps.

In the meantime, this is what Claude found when looking at the code. I think all points are worth addressing:

Bugs

1. navigate() called during render instead of in an effect

In src/authz-module/audit-user/index.tsx#L29-L31:

if (!user && !isLoadingUser) {
    navigate(AUTHZ_HOME_PATH);
}

This is a side effect executed during render. React will warn about state updates during render, and the component continues executing (building navLinks, columns, etc.) after the navigate call, which is wasted work at best and can lead to errors if it references state that becomes inconsistent mid-render.

The existing LibrariesUserManager.tsx handles the same pattern correctly:

useEffect(() => {
    if (!isFetchingMember) {
        if (!isLoadingTeamMember && !user?.username) {
            navigate(teamMembersPath);
        }
    }
}, [isFetchingMember, isLoadingTeamMember, user?.username]);

2. Pagination uses results.length instead of API count

In src/authz-module/audit-user/index.tsx#L74 and #L102:

const pageCount = userAssignments?.length
    ? Math.ceil(userAssignments.length / TABLE_DEFAULT_PAGE_SIZE) : 1;
...
itemCount={userAssignments?.length || 0}

The table is configured with manualPagination, meaning the backend handles pagination and the frontend must tell the table how many total items exist. But itemCount and pageCount are derived from userAssignments.length - which is the current page of results, not the total. The API response includes a count field for the total, but the component destructuring on line 27 doesn't extract it:

const { data: { results: userAssignments } = { results: [] } } = useUserAssignedRoles(...);

When a user has more than 10 role assignments, the table will display only the first page with no way to navigate further.

3. UserAccount type uses snake_case but camelCaseObject is applied

src/data/api.ts#L14-L17 applies camelCaseObject(data) to the API response, but the UserAccount type defines fields in snake_case (profile_image, date_joined, is_active, etc.). The actual runtime data has camelCase keys (profileImage, dateJoined, isActive), so the TypeScript types are incorrect.

This doesn't break the two fields actually used - username and email - since they're unchanged by camelCasing. But it's a type safety gap. The test mock data in src/data/hooks.test.tsx already uses camelCase (profileImage, dateJoined), confirming the mismatch. Either the type should use camelCase or camelCaseObject should be removed.

Issues

4. Route ordering in AuthZModule

In src/authz-module/index.tsx#L33-L36, the AUDIT_USER_PATH route is declared after the path="*" catch-all:

<Route path="*" element={<NotFoundError />} />
<Route path={ROUTES.AUDIT_USER_PATH} element={<AuditUserPage />} />

React Router v6 ranks routes by specificity so this works correctly at runtime (/user/:username is more specific than *). But conventionally catch-all routes go last - having it in the middle makes the routing table harder to read and will mislead anyone unfamiliar with v6's ranking behavior.

5. CSS breakpoint gap for hr styling

The SCSS was refactored from a desktop-up approach to a mobile-first approach. On master, hr defaults to horizontal and switches to vertical at min-width-lg. In the new code (index.scss#L7-L10 and #L54-L63), it defaults to vertical and only switches to horizontal at max-width-sm. Screens between sm and lg breakpoints will now see vertical hr dividers where they previously saw horizontal ones. If this is intentional to match the new Figma designs, no issue - just flagging it since it affects the existing library pages too (via the class rename from .authz-libraries to .authz-module).

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

I started by trying to test this. The page renders correctly

If that's the case, please add the backend PRs that this depends on to the description. Otherwise, if there's some other way to test this, please outline the steps.

In the meantime, this is what Claude found when looking at the code. I think all points are worth addressing:

Bugs

1. navigate() called during render instead of in an effect
2. Pagination uses results.length instead of API count
3. UserAccount type uses snake_case but camelCaseObject is applied
4. Route ordering in AuthZModule
5. CSS breakpoint gap for hr styling

Hi @arbrandes, thanks for your review. The "Assign Role" button is not depending in any endpoint, it redirects to the role assignation wizard currently in development (here #109 and #111), I just added the redirection to that page but it does not exist yet thats why it shows 404, (Should I remove the button and add it when the wizard is done? So it does not become a blocker for this pr).

Also I have addressed the rest of the points:

  1. navigate() moved to a useEffect
  2. I had already replaced the results.length with count but I guess I mixed it up during a merge conflicts resolution, I did it again.
  3. Changed all the fields on UserAccount to camelCase.
  4. Route ordering fixed.
  5. This is an intentional change so the ui does not break on small screens and matches the figma design.

@arbrandes
Copy link
Copy Markdown
Contributor

Should I remove the button and add it when the wizard is done?

No need, but I still need a way to add data so I can see it working. How do I do that?

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

Should I remove the button and add it when the wizard is done?

No need, but I still need a way to add data so I can see it working. How do I do that?

Since the wizard is currently on development, you can still use the old library specific access manager and assign some roles there http://apps.local.openedx.io:2025/admin-console/authz/libraries/:libraryId

Or, can assign roles by directly using the api endpoint http://local.openedx.io:8000/api-docs/#/authz/authz_v1_roles_users_update

I will add it to the testing instructions in the description.

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

Can you give another look @arbrandes, @dcoa?

@arbrandes
Copy link
Copy Markdown
Contributor

Ok, thank you! I tried it out, and here were the results.

With the version of the MFE in master, I successfully added all four roles to a user named erik using the library interface at http://apps.local.openedx.io:2025/admin-console/authz/libraries/lib:Axim:TL01:

image

I then checked out this PR and went to http://apps.local.openedx.io:2025/admin-console/authz/user/erik, but the spinner appears for a long time and in the end I see nothing:

image

I am on an up-to-date edx-platform.

Claude also caught a couple of remaining issues:

1. ProfileImage type still uses snake_case

The previous review flagged that the UserAccount type used snake_case despite camelCaseObject being applied in src/data/api.ts#L17. The UserAccount fields were correctly converted to camelCase, but the nested ProfileImage type was missed:

export type ProfileImage = {
  has_image: boolean;
  image_url_full: string;
  image_url_large: string;
  image_url_medium: string;
  image_url_small: string;
};

camelCaseObject transforms nested objects recursively, so the runtime data has hasImage, imageUrlFull, etc. The test mock data in src/data/hooks.test.tsx#L53-L59 already uses the correct camelCase keys, confirming the mismatch.

2. CSS class rename missed in authz-home

The SCSS file renames .authz-libraries to .authz-module (src/authz-module/index.scss#L3). The class name was updated in LibrariesTeamManager.tsx and LibrariesUserManager.tsx, but src/authz-module/authz-home/index.tsx#L17 still uses the old name:

<div className="authz-libraries">

Since .authz-libraries no longer exists in the stylesheet, the authz-home page loses all styles scoped under that class: hr divider styling, .tab-content background, .collapsible-card and .collapsible-trigger overrides, .permission-chip sizing, .permission-table line height, and the responsive @media rules.

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

jacobo-dominguez-wgu commented Apr 16, 2026

Ok, thank you! I tried it out, and here were the results.
I then checked out this PR and went to http://apps.local.openedx.io:2025/admin-console/authz/user/erik, but the spinner appears for a long time and in the end I see nothing:

All the required backend prs are merged into main now for openedx-authz, but I am guessing openedx-platform does not have the latest version of openedx-authz, I tried mounting main branch of openedx-authz and the table loaded for me, would you mind trying?

Update:
It is included on openedx-platform now openedx/openedx-platform#38348

1. ProfileImage type still uses snake_case

This one is addressed.

2. CSS class rename missed in authz-home

Solved

Thanks @arbrandes.

@dcoa
Copy link
Copy Markdown
Contributor

dcoa commented Apr 17, 2026

I can validate all the acceptance criteria, except this:

The table is paginated with 10 rows per page, with previous/next arrows and a page selector using the Paragon reduce variant.

There is not page selector in the table. There is a reason for that? @jacobo-dominguez-wgu

I also can see some errors that were addressed here #124 , so I suggest merge that and rebase this before approved. In general looks good.

Comment thread src/authz-module/data/hooks.ts Outdated
Comment on lines +127 to +135
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => {
const result = useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});
return result;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => {
const result = useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});
return result;
};
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a reason for this values?

    staleTime: 1000 * 60 * 30, // refetch after 30 minutes
    refetchOnWindowFocus: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you this change has been addressed!
about this

staleTime: 1000 * 60 * 30, // refetch after 30 minutes

I think is the time Jacobo considered correct for the data to be current
do you have any suggestions? or can we leave it this way?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the refetchOnWindowFocus there was a unnecessary addition to the previous implementation. In terms of the stale time for now is okay but that is something that need to evaluate in the future with performance data.

Copy link
Copy Markdown
Contributor

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I finally got this to work (I had to rebuild my openedx-dev image from scratch to get the latest openedx-authz), and it looks good! The only weird thing I saw is a blank role with asterisks:

Image

Not a blocker, just curious. (I suspect it's just stale data.)

@jesusbalderramawgu
Copy link
Copy Markdown
Contributor

Ok, I finally got this to work (I had to rebuild my openedx-dev image from scratch to get the latest openedx-authz), and it looks good! The only weird thing I saw is a blank role with asterisks:

Image Not a blocker, just curious. (I suspect it's just stale data.)

Thank you Adolfo, that issue is gonna be solved in one of my PR's later

@jesusbalderramawgu
Copy link
Copy Markdown
Contributor

I can validate all the acceptance criteria, except this:

The table is paginated with 10 rows per page, with previous/next arrows and a page selector using the Paragon reduce variant.

There is not page selector in the table. There is a reason for that? @jacobo-dominguez-wgu

I also can see some errors that were addressed here #124 , so I suggest merge that and rebase this before approved. In general looks good.

Thanks for your feedback, I checked and I see the page selector correctly but is not visible if we have less than 10 records
we have right now this constants
TABLE_DEFAULT_PAGE_SIZE = 10
so if we have 10 or less records the page selector won't be visible unless we have more records to show
also I checked and looks exactly like in the design.

Screenshot 2026-04-17 at 9 16 16 a m

@dcoa dcoa changed the title Adding roles table for Audit user page feat(authz): add roles table for Audit user page Apr 18, 2026
@jacobo-dominguez-wgu jacobo-dominguez-wgu moved this from Waiting on Author to Ready for Review in Contributions Apr 18, 2026
@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

Rebased and conflicts solved. @dcoa could you review and merge please?

Comment thread src/authz-module/audit-user/index.tsx Outdated
const { formatMessage } = useIntl();
const { username } = useParams();
const navigate = useNavigate();
const { isLoading: isLoadingUser, data: user } = useUserAccount(username ?? '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid this username ?? '' conditional add enabled: !!username as a flag in the hook instead, so the API call is only trigger when a username exist. I'm not counting this as a blocker because it can be solved in a subsequent PR as a minor fix. @jacobo-dominguez-wgu

Copy link
Copy Markdown
Contributor

@dcoa dcoa Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this behavior found by Claude:

the "not found" redirect treats any falsy user as "navigate away." If username is empty,
useUserAccount is disabled (data=undefined, isLoading=false) and the effect fires immediately — fine behaviorally, but if getUserAccount errors (5xx,
network), data is also undefined, so users get silently booted to home instead of seeing an error. Consider checking isError/error.status === 404
explicitly.

src/authz-module/audit-user/index.tsx:41-76 — columns / additionalColumns / navLinks are rebuilt on every render. fetchData is memoized but the
column arrays aren't; stabilize them with useMemo since DataTable uses referential identity.

src/authz-module/audit-user/index.tsx:79 — the debounced fetchData isn't cancelled on unmount. Add a cleanup effect calling fetchData.cancel() to avoid a setState-after-unmount warning.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All has been gathered here #132

Copy link
Copy Markdown
Contributor Author

@jacobo-dominguez-wgu jacobo-dominguez-wgu Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed all of them on the last commit, thanks @dcoa

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was still a couple of comments left so they are in the issue #132

const handleDelete = () => {
// TODO: Implement delete functionality
// eslint-disable-next-line no-console
console.log('Delete clicked for row:', row);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please for traceability mention here the issue or PR that will implement this @jacobo-dominguez-wgu

Copy link
Copy Markdown
Contributor

@dcoa dcoa Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete issue #89

return (
<ViewMoreLink
label={formatMessage(messages['authz.user.table.view_all_permissions.link.text'])}
// TODO: Implement view more functionality
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the ticket #87

@dcoa dcoa merged commit 411e3d3 into openedx:master Apr 18, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Contributions Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Task - RBAC AuthZ - M2.6 View the audit detail for a specific user - Replace roles card view with roles table

6 participants